#ifndef OPTION_PARSER_HPP
#define OPTION_PARSER_HPP
/*
    Copyright (c) 2009-10 Qtrac Ltd. All rights reserved.

    This program or module is free software: you can redistribute it
    and/or modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation, either version 3 of
    the License, or (at your option) any later version. It is provided
    for educational purposes and is distributed in the hope that it will
    be useful, but WITHOUT ANY WARRANTY; without even the implied
    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
    the GNU General Public License for more details.
*/

#include <QCoreApplication>
#include <QSet>
#include <QSharedPointer>
#include <QString>
#include <QStringList>
#include <QVariant>
#include <QTextCodec>

#include <utils_global.hpp>

class SHARED_EXPORT PrivateOption
{
public:
    explicit PrivateOption(const QString &shortName=QString(),
                           const QString &longName=QString())
        : m_shortName(shortName),
          m_longName(longName),
          m_required(false)
        {
            Q_ASSERT(!(longName.isEmpty() && shortName.isEmpty()));
        }
    virtual ~PrivateOption() {}

    bool         required() const { return m_required; }
    virtual void setRequired(bool on=true) { m_required = on; }
    QString      help() const { return m_help; }
    void         setHelp(const QString &help) { m_help = help; }
    bool         hasHelp() const { return !m_help.isEmpty(); }

    QString      shortName() const { return m_shortName; }
    QString      longName() const { return m_longName; }

    bool         hasValue() const { return m_value.isValid(); }
    bool         hasDefaultValue() const { return m_defaultValue.isValid(); }

    virtual QVariant::Type type() const = 0;

protected:
    QVariant      m_defaultValue;
    QVariant      m_value;

private:
    const QString m_shortName;
    const QString m_longName;
    bool          m_required;
    QString       m_help;
};


class SHARED_EXPORT IntegerOption : public PrivateOption
{
public:
    explicit IntegerOption(const QString &shortName=QString(),
                           const QString &longName=QString())
        : PrivateOption(shortName, longName) {}

    int       defaultValue       () const { return m_defaultValue.toInt(); }
    void      setDefaultValue    (int value) { m_defaultValue = value; }
    bool      hasDefaultValue    () const { return m_defaultValue.isValid(); }
    int       minimum            () const { return m_minimum.toInt(); }
    void      setMinimum         (int minimum) { m_minimum = minimum; }
    bool      hasMinimum         () const { return m_minimum.isValid(); }
    int       maximum            () const { return m_maximum.toInt(); }
    void      setMaximum         (int maximum) { m_maximum = maximum; }
    bool      hasMaximum         () const { return m_maximum.isValid(); }
    void      setRange           (int minimum, int maximum)
                                 { m_minimum = minimum; m_maximum = maximum; }
    QSet<int> acceptableValues   () const { return m_acceptableValues; }
    void      setAcceptableValues(const QList<int> &acceptableValues)
                                 { m_acceptableValues = QSet<int>::fromList(acceptableValues); }
    void      setAcceptableValues(const QSet<int> &acceptableValues)
                                 { m_acceptableValues = acceptableValues; }
    int       value              () const
                                 { return m_value.toInt(); }
    void      setValue           (int value)
                                 { m_value = value; }
    bool      hasAcceptableValues() const
                                 { return !m_acceptableValues.isEmpty(); }

    int       integer            () const
                                 { return m_value.toInt(); }
    QVariant::Type type          () const
                                 { return QVariant::Int; }

private:
    QVariant  m_minimum;
    QVariant  m_maximum;
    QSet<int> m_acceptableValues;
};


class SHARED_EXPORT RealOption : public PrivateOption
{
public:
    explicit RealOption(const QString &shortName=QString(),
                        const QString &longName=QString())
        : PrivateOption(shortName, longName) {}

    double defaultValue() const { return m_defaultValue.toInt(); }
    void setDefaultValue(double value) { m_defaultValue = value; }
    bool hasDefaultValue() const { return m_defaultValue.isValid(); }
    double minimum() const { return m_minimum.toInt(); }
    void setMinimum(double minimum) { m_minimum = minimum; }
    bool hasMinimum() const { return m_minimum.isValid(); }
    double maximum() const { return m_maximum.toInt(); }
    void setMaximum(double maximum) { m_maximum = maximum; }
    bool hasMaximum() const { return m_maximum.isValid(); }
    void setRange(double minimum, double maximum)
        { m_minimum = minimum; m_maximum = maximum; }
    double value() const { return m_value.toDouble(); }
    void setValue(double value) { m_value = value; }

    double real() const { return m_value.toDouble(); }
    QVariant::Type type() const { return QVariant::Double; }

private:
    QVariant m_minimum;
    QVariant m_maximum;
};


class SHARED_EXPORT BooleanOption : public PrivateOption
{
public:
    explicit BooleanOption(const QString &shortName=QString(),
                           const QString &longName=QString())
        : PrivateOption(shortName, longName) {}

    bool defaultValue() const { return m_defaultValue.toInt(); }
    void setDefaultValue(bool value) { m_defaultValue = value; }
    bool hasDefaultValue() const { return m_defaultValue.isValid(); }
    bool value() const { return m_value.toBool(); }
    void setValue(bool value) { m_value = value; }

    bool boolean() const { return m_value.toBool(); }
    QVariant::Type type() const { return QVariant::Bool; }

    void setRequired(bool=true) { Q_ASSERT(false); }
};


class SHARED_EXPORT StringOption : public PrivateOption
{
public:
    explicit StringOption(const QString &shortName=QString(),
                          const QString &longName=QString())
        : PrivateOption(shortName, longName) {}

    QString defaultValue() const { return m_defaultValue.toString(); }
    void setDefaultValue(const QString &value) { m_defaultValue = value; }
    bool hasDefaultValue() const { return m_defaultValue.isValid(); }
    QSet<QString> acceptableValues() const { return m_acceptableValues; }
    void setAcceptableValues(const QStringList &acceptableValues)
        { m_acceptableValues = QSet<QString>::fromList(acceptableValues); }
    void setAcceptableValues(const QSet<QString> &acceptableValues)
        { m_acceptableValues = acceptableValues; }
    bool hasAcceptableValues() const
        { return !m_acceptableValues.isEmpty(); }
    QString value() const { return m_value.toString(); }
    void setValue(const QString &value) { m_value = value; }

    QString string() const { return m_value.toString(); }
    QVariant::Type type() const { return QVariant::String; }

private:
    QSet<QString> m_acceptableValues;
};



typedef QSharedPointer<PrivateOption> OptionPtr;
typedef QSharedPointer<IntegerOption> IntegerOptionPtr;
typedef QSharedPointer<RealOption>    RealOptionPtr;
typedef QSharedPointer<BooleanOption> BooleanOptionPtr;
typedef QSharedPointer<StringOption>  StringOptionPtr;



class SHARED_EXPORT OptionParser
{
    Q_DECLARE_TR_FUNCTIONS(OptionParser)

public:
    explicit OptionParser(const QStringList &arguments = QStringList(),
                          const QString     &help      = QString    (),
                          const QString     &copyright = QString    ()
                         );

    bool parse();
    int  usage(const QString &error=QString());

    int         integer  (const QString &name) const;
    double      real     (const QString &name) const;
    bool        boolean  (const QString &name) const;
    QString     string   (const QString &name) const;
    bool        hasValue (const QString &name) const { return m_options[name]->hasValue(); }
    QStringList remainder()                    const { return m_remainder; }
    OptionPtr   option   (const QString &name)       { return m_options[name]; }

    IntegerOptionPtr addIntegerOption(const QString &shortName=QString(),
                                      const QString &longName=QString());
    RealOptionPtr    addRealOption   (const QString &shortName=QString(),
                                      const QString &longName=QString());
    BooleanOptionPtr addBooleanOption(const QString &shortName=QString(),
                                      const QString &longName=QString());
    StringOptionPtr  addStringOption (const QString &shortName=QString(),
                                      const QString &longName=QString());

    bool             helpPrinted() const {return m_helpprinted;}

private:
    BooleanOptionPtr addHelpOption();
    void             addOption(OptionPtr option);

    QStringList              m_arguments;
    QString                  m_help;
    QString                  m_copyright;
    QStringList              m_remainder;
    QMap<QString, OptionPtr> m_options;
    bool                     m_helpprinted;
};


#endif // OPTION_PARSER_HPP
